
package com.gitee.jmash.oidc.oauth2.jaxrs;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
import java.util.Arrays;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.util.encoders.Hex;
import org.eclipse.microprofile.jwt.JsonWebToken;
import com.gitee.jmash.common.lock.DistributedLock;
import com.gitee.jmash.common.security.DefaultJmashPrincipal;
import com.gitee.jmash.common.security.JmashPrincipal;
import com.gitee.jmash.common.utils.UUIDUtil;
import com.gitee.jmash.crypto.cipher.CipherUtil;
import com.gitee.jmash.crypto.cipher.SM4CbcUtil;
import com.gitee.jmash.oidc.oauth2.OauthServerProps;
import com.gitee.jmash.oidc.oauth2.enums.ResponseType;
import com.gitee.jmash.oidc.oauth2.enums.ScopeType;
import com.gitee.jmash.oidc.oauth2.models.AuthzModel;
import com.gitee.jmash.oidc.oauth2.models.ErrorResponse;
import com.gitee.jmash.oidc.oauth2.utils.IdTokenUtil;
import com.gitee.jmash.oidc.web.Constant;
import com.gitee.jmash.rbac.RbacFactory;
import com.gitee.jmash.rbac.entity.TokenEntity;
import com.gitee.jmash.rbac.model.TokenAuthzCodeReq;
import com.xyvcard.ops.OpsFactory;
import com.xyvcard.ops.entity.OpsClientEntity;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

/**
 * OAuth2.0 /oauth/authorize.
 *
 * @author CGD
 *
 */
@Path("/authorize")
public class AuthorizeEndpoint {

  @Inject
  OauthServerProps oauthServerProps;

  @Inject
  DistributedLock lock;

  public String decode(String value) throws Exception {
    CipherUtil cipherUtil = SM4CbcUtil.get();
    byte[] key = cipherUtil.decodeKey(oauthServerProps.getSm4key());
    byte[] content = Hex.decode(value);
    byte[] result = cipherUtil.decrypt(key, content);
    return new String(result);
  }

  /** /oauth/authorize Code/Token Type Request. */
  @GET
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  @Produces(MediaType.APPLICATION_JSON)
  public Response authorize(@Valid @BeanParam AuthzModel authz,
      @Context HttpServletRequest request) {
    // Key校验.
    if (StringUtils.isNotBlank(authz.getKey())) {
      try {
        String result = decode(authz.getKey());
        if (!result.startsWith(authz.getClientId())) {
          return ErrorResponse.createResponse("invalid_request", "Key Error,client_id Not Match .");
        }
      } catch (Exception ex) {
        return ErrorResponse.createResponse("invalid_request", "Key Error,Default '' .");
      }
    }
    // 客户端ID校验
    OpsClientEntity client =
        OpsFactory.getOpsClientRead(Constant.TENANT).findById(authz.getClientId());
    if (null == client) {
      return ErrorResponse.createResponse("unauthorized_client", "client_id 未注册.");
    }

    if (StringUtils.isBlank(authz.getRedirectUri()) && client.getRedirectUriArray().length != 1) {
      return ErrorResponse.createResponse("invalid_request", "缺少redirect_uri参数.");
    }

    if (StringUtils.isBlank(authz.getRedirectUri()) && client.getRedirectUriArray().length == 1) {
      authz.setRedirectUri(client.getRedirectUriArray()[0]);
    }

    if (client.getRedirectUriArray().length > 1
        && Arrays.binarySearch(client.getRedirectUriArray(), authz.getRedirectUri()) < 0) {
      return ErrorResponse.createResponse("invalid_request", "redirect_uri与客户端注册不一致.");
    }

    // 1.用户登录并授权.
    final Principal principal = request.getUserPrincipal();
    if (null == principal) {
      return authorizeUrl(request);
    }

    // 2.用户授权应用页面.
    UUID accountId = UUIDUtil.fromString(principal.getName());
    TokenEntity token = RbacFactory.getUserRead(Constant.TENANT)
        .findTokenById(new TokenEntity.TokenPk(accountId, authz.getClientId()));
    //二次进入,仅获取OpenId,无需授权,获取用户个人信息需授权页面.
    if ((null == token || !StringUtils.equals(ScopeType.openid.name(), authz.getScope()))
        && StringUtils.isBlank(authz.getKey())) {
      return authorizeUrl(request);
    }

    // 3.检查是否重复请求.
    if (StringUtils.isNotBlank(authz.getNonce())) {
      if (!lock.lock(authz.getNonce(), 60)) {
        return ErrorResponse.createResponse("invalid_request", "nonce重复请求.");
      }
    }

    TokenAuthzCodeReq req = new TokenAuthzCodeReq();
    req.setUserId(accountId);
    req.setClientId(authz.getClientId());
    req.setScope(authz.getScope());
    req.setRedirectUri(authz.getRedirectUri());
    if (principal instanceof JsonWebToken) {
      JsonWebToken webToken = (JsonWebToken) principal;
      JmashPrincipal jmashPrincipal = DefaultJmashPrincipal.create(webToken);
      req.setIssuer(webToken.getIssuer());
      req.setSubject(webToken.getSubject());
      req.setUnifiedId(jmashPrincipal.getUnifiedId());
      req.setStorage(jmashPrincipal.getStorage());
    }

    // 授权码（authorization code）
    if (ResponseType.hasCode(authz.getResponseType())) {
      // 3.Authz Code.
      token = RbacFactory.getUserWrite(Constant.TENANT).createAuthzCode(req);
      URI location = redirectUriByCode(authz, token);
      return Response.temporaryRedirect(location).build();
    } else if (ResponseType.hasImplicit(authz.getResponseType())) {
      // 隐藏式 implicit -> access_token
      token = RbacFactory.getUserWrite(Constant.TENANT).implicitToken(req);
      URI location = redirectUriByToken(authz, token);
      return Response.temporaryRedirect(location).build();
    }
    return ErrorResponse.createResponse("unsupported_response_type", "response_type Not Fund.");
  }

  /**
   * 跳转到Callback地址.
   */
  public static URI redirectUriByToken(AuthzModel authz, TokenEntity token) {
    try {
      StringBuilder queryString = new StringBuilder("");
      if (StringUtils.isNotBlank(authz.getScope())) {
        queryString.append("scope=openid");
      }
      if (StringUtils.isNotBlank(authz.getState())) {
        queryString.append("&state=" + authz.getState());
      }
      if (ResponseType.hasAccessToken(authz.getResponseType())) {
        queryString
            .append("&token=" + token.getAccessToken() + "&token_type=" + token.getTokenType());
      }
      // id_token
      if (ResponseType.hasIdToken(authz.getResponseType())
          && StringUtils.contains(authz.getScope(), "openid")) {
        String idToken = IdTokenUtil.createIdToken(token);
        queryString.append("&id_token=" + idToken);
      }
      return new URI(authz.getRedirectUri() + (authz.getRedirectUri().contains("?") ? "&" : "?")
          + queryString);
    } catch (URISyntaxException ex) {
      LogFactory.getLog(AuthorizeEndpoint.class).error("地址错误:" + authz.getRedirectUri(), ex);
      return null;
    }
  }

  /**
   * 跳转到Callback地址.
   */
  public static URI redirectUriByCode(AuthzModel authz, TokenEntity token) {
    try {
      StringBuilder queryString = new StringBuilder("code=" + token.getAuthorizationCode());
      if (StringUtils.isNotBlank(authz.getState())) {
        queryString.append("&state=" + authz.getState());
      }
      // id_token
      if (ResponseType.hasIdToken(authz.getResponseType())
          && StringUtils.contains(authz.getScope(), "openid")) {
        String idToken = IdTokenUtil.createIdToken(token);
        queryString.append("&id_token=" + idToken);
      }
      return new URI(authz.getRedirectUri() + (authz.getRedirectUri().contains("?") ? "&" : "?")
          + queryString);
    } catch (URISyntaxException ex) {
      LogFactory.getLog(AuthorizeEndpoint.class).error("地址错误:" + authz.getRedirectUri(), ex);
      return null;
    }
  }

  /**
   * 跳转到授权页面.
   */
  public static Response authorizeUrl(HttpServletRequest request) {
    try {
      StringBuffer backurl =
          new StringBuffer(request.getContextPath() + "/oauth/private/authorize?");
      backurl.append(request.getQueryString());
      URI location = new URI(backurl.toString());
      return Response.temporaryRedirect(location).build();
    } catch (URISyntaxException ex) {
      LogFactory.getLog(AuthorizeEndpoint.class).error("地址错误！", ex);
      return null;
    }
  }

}
